package 多线程3;
//定时器的实现
//标准库中提供了一个 Timer 类. Timer 类的核心方法为 schedule .
//schedule 包含两个参数. 第一个参数指定即将要执行的任务代码, 第二个参数指定多长时间之后
//执行 (单位为毫秒)

import javafx.scene.layout.Priority;

import java.util.PriorityQueue;
import java.util.concurrent.PriorityBlockingQueue;

/**
 * 一个定时器里面是可以安排很多任务的
 * 这些任务就会按照时间,谁先到了时间,就先执行谁
 * 描述任务就会按照时间,谁先到了时间,就先执行谁
 *
 * 描述任务:就可以直接使用runnable来描述这个任务.
 * 组织任务:就需要有一个数据结构,把很多的任务给放到一起
 *  此处的需求是需要能够在一大堆任务中,找到那个最先要到点的任务~~
 *  优先队列(带阻塞的)-- 标准库里有
 *
 *  提供一个schedule方法,这个方法就是往阻塞队列里面中插入元素就行了
 *  还需要让timier内部有一个线程,这个线程一直去扫描队首元素,看看队首元素
 *  是不是已经到点了,如果到点,就执行这个任务,如果没到点,就把这个队首元素塞回队列中,继续扫描
 *
 */
public class TestDemo6 {
//使用这个类来描述任务
    static class Task implements Comparable<Task>{
        //command 表示这个任务是啥
        private Runnable command;
        //time表示这个任务啥时候到时间
        //这里的time使用ms级得到时间戳来表示
        private long time;
        public void run(){
            command.run();
        }
        //约定参数time是一个时间差(类似于3000)
        //希望this.time来保存一个绝对的时间.(毫秒级时间戳)
        public Task(Runnable command, long time) {
            this.command = command;
            this.time = System.currentTimeMillis() + time;
        }

    @Override
    public int compareTo(Task o) {
        return (int)(this.time - o.time);
    }
}
    static class Timer{
     //使用这个带优先级版本的阻塞队列来组织这些任务
        private PriorityBlockingQueue<Task> queue = new PriorityBlockingQueue<>();
        //使用这个locker对象来解决忙等问题.
        private Object locker = new Object();

        public void schedule(Runnable command,long delay){
            Task task = new Task(command,delay);
            queue.put(task);
            //每次插入新的任务,都要唤醒扫描线程.让扫描线程能够重新计算wait的时间,保证新的任务也不会错过
            synchronized(locker){
                locker.notify();
            }
        }

        public Timer(){
            //创建一个扫描线程，这个扫描线程就来判断当前的任务，看看是不是已经到时间能执行了
            Thread t = new Thread(){
                @Override
                public void run() {
                    /**
                     * 扫描的速度太快了
                     * 例如,最早的任务是8:00执行,但是当前才7:00
                     * 这个循环显然不会执行当前的任务
                     * 这个时候这个循环会转的非常快,一秒能转几万次
                     *   转的这么快,没有意义,完全就是空转,浪费CPU
                     *   这种情况,称为"忙等"
                     * 使用sleep?? 不建议,因为需要给定时间,而且时间你是不知道需要多少的
                     * 使用wait更好一些
                     * 使用wait也可以指定一个时间作为参数,就是等待的时间,等待的时间
                     * 可以通过当前时刻和首个任务之间的时间间隔来算.
                     * 当前7:00
                     * 第一个任务是8:00
                     * wait 1hour是能提前唤醒的,但是sleep不行.
                     * 如果过了一会,插入一个新的任务,新的任务是7:30执行的.
                     *
                     * 如果使用sleep,一旦插入新的任务,比原有任务还近,就僵住了,sleep没法提前唤醒就错过了
                     * 这个新任务的时间.
                     * 如果使用wait,一旦插入新的任务,可以使用notify来唤醒,就可以及时的感知到新任务啥时候执行了
                     *
                     */
                    while(true){
                        //取出队列的首元素，判定时间是不是已经到了
                        Task task = null;
                        try {
                            task = queue.take();
                            long curTime = System.currentTimeMillis();
                            if (task.time > curTime){
                                //时间还没到,暂时不执行
                                //前面的take操作会把队首元素给删除掉.
                                //但是此时队首元素的任务还没有执行呢,不能删除,于是需要重新插入回队列
                                queue.put(task);
                                //根据时间差,来进行一个等待
                                synchronized (locker){
                                    locker.wait(task.time-curTime);
                                }
                            }else{
                                //时间到了
                                task.run();
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                            //如果出现了interrupt方法,就能够退出线程
                            break;
                        }

                    }
                }
            };
            t.start();
        }
    }
    //自己的定时器Timer
    public static void main(String[] args) {
        System.out.println("程序启动");
        Timer timer = new Timer();
        timer.schedule(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello");
            }
        },3000);

    }
}
